package main

import (
	"bytes"
	"fmt"
	"runtime"
	"strconv"
)

// 使用 defer 跟踪函数的执行过程
func main() {
	defer Trace("main")()
	foo()

	/*输出:
	enter: main
	enter: foo
	enter: bar
	exit: bar
	exit: foo
	exit: main
	可以看到, 程序按 main -> foo -> bar 的函数调用次序执行，并且逐层返回.
	*/
}

func Trace(name string) func() {
	println("enter:", name)
	return func() {
		println("exit:", name)
	}
}

/**
实现一个自动注入跟踪代码，并输出有层次感的函数调用链跟踪命令行工具.
*/

func TraceV2() func() {
	//通过 runtime.Caller 函数获得当前 Goroutine 的函数调用栈上的信息，
	// runtime.Caller 的参数标识的是要获取的是哪一个栈帧的信息。
	//当参数为 0时，返回的是 Caller 函数的调用者的函数信息，在这里就是 Trace 函数。
	// 但我们需要的是Trace 函数的调用者的信息，于是我们传入 1。

	//Caller 函数有四个返回值：
	// 第一个返回值代表的是程序计数（pc）；
	// 第二个和第三个参数代表对应函数所在的源文件名以及所在行数，这里我们暂时不需要；
	// 最后一个参数代表是否能成功获取这些信息，如果获取失败，我们抛出 panic。
	pc, _, _, ok := runtime.Caller(1)
	if !ok {
		panic("not found caller")
	}

	//通过 runtime.FuncForPC 函数和程序计数器（PC）得到被跟踪函数的函数名称
	//runtime.FuncForPC 返回的名称中不仅仅包含函数名，还包含了被跟踪函数所在的包名。
	fn := runtime.FuncForPC(pc)
	name := fn.Name()

	//增加 增加 Goroutine 标识 前:
	//println("enter:", name)
	//return func() {
	//	println("exit:", name)
	//}

	// 增加 Goroutine 标识 后:
	gid := curGoroutineID()
	fmt.Printf("g[%05d]: enter: [%s]\n", gid, name) //输出的 Goroutine ID 为 5 位数字，如果 ID 值不足 5 位，则左补零，这一切都是 Printf 函数的格式控制字符串“%05d”帮助我们实现的。
	return func() {
		fmt.Printf("g[%05d]: exit: [%s]\n", gid, name)
	}
}

/*
第二个问题，也就是当程序中有多 Goroutine 时，Trace 输出的跟踪信息混杂在一起难以分辨的问题。
解决办法:增加 Goroutine 标识. 在输出的函数出入口信息时，带上一个在程序每次执行时能唯一区分Goroutine 的 Goroutine ID。
个获取 Goroutine ID 的标准方法, 参考: // $GOROOT/src/net/http/h2_bundle.go
*/
var goroutineSpace = []byte("goroutine ")

func curGoroutineID() uint64 {
	b := make([]byte, 64)
	b = b[:runtime.Stack(b, false)]
	// Parse the 4707 out of "goroutine 4707 ["
	b = bytes.TrimPrefix(b, goroutineSpace)
	i := bytes.IndexByte(b, ' ')
	if i < 0 {
		panic(fmt.Sprintf("No space found in %q", b))
	}
	b = b[:i]
	n, err := strconv.ParseUint(string(b), 10, 64)
	if err != nil {
		panic(fmt.Sprintf("Failed to parse goroutine ID out of %q: %v", b, err))
	}
	return n
}

func foo() {
	defer Trace("foo")()
	bar()
}
func bar() {
	defer Trace("bar")()
}
